package com.netflexitysolutions.amazonws.sdb.orm.internal.generator;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.Resource;

import com.netflexitysolutions.amazonws.sdb.orm.SimpleDBSession;
import com.netflexitysolutions.amazonws.sdb.orm.internal.util.StringConstants;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.ItemMetadata;
import com.netflexitysolutions.amazonws.sdb.orm.metadata.SimpleDBMapping;

import freemarker.template.SimpleScalar;
import freemarker.template.TemplateException;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModelException;

/**
 * @author Max Fedorov
 *
 * Running this class with appropriate options will produce all the necessary
 * metadata information for any targeted database.
 */
public class OrmGenerator {

    public static final String MAPPING = "mapping";
	public static final String BASE = "Base";
    public static final String Y_ENDING = "y";
    public static final String S_ENDING = "s";
    public static final String ES_ENDING = "es";
    public static final String SS_ENDING = "ss";
    public static final String IES_ENDING = "ies";
    
    public static final String BASE_CLASS = "baseClass";
	public static final String BASE_PACKAGE = "basePackage";
    public static final String PACKAGE_STR = "package";
    public static final String META_FTL = "meta.ftl";
	public static final String TABLE = "table";
	public static final String CLASS = "class";
	public static final String JAVA_EXT = ".java";
	public static final String TABLES = "tables";
	public static final String DORM_XML = "orm.xml";
	public static final String DOMAIN_CLASSES_FTL = "domainClasses.ftl";
    
	public static final String PASSWORD_OPT = "p";
	public static final String USERNAME_OPT = "u";
	public static final String URL_OPT = "r";
	public static final String CATALOG_OPT = "c";
	public static final String SCHEMA_OPT = S_ENDING;
	public static final String DRIVER_OPT = "v";
	public static final String PACKAGE_OPT = "g";
	public static final String DIR_OPT = "d";
    public static final String DOMAIN_FTL_OPT = "o";
    public static final String BASE_FTL_OPT = "b";
    public static final String FILE_EXTENSION_OPT = "e";
    
    private SimpleDBMapping mapping;
    private FreemarkerTemplateManager templateManager;
    private File domainDirectory;
	private String domainPackageName;
	
	private String domainClassTemple = DOMAIN_CLASSES_FTL;
	private String fileExtension = JAVA_EXT;
	
	/**
	 * 
	 */
	public OrmGenerator() {
	}
	
	/**
	 * @param session
	 * @param templateManager
	 * @param domainDirectory
	 * @param domainPackageName
	 */
	public OrmGenerator(SimpleDBSession session, FreemarkerTemplateManager templateManager, Resource domainDirectory, String domainPackageName) throws IOException{
		this.mapping = session.getFactory().getMapping();
		this.templateManager = templateManager;
		this.domainDirectory = domainDirectory.getFile();
		this.domainPackageName = domainPackageName;
	}
	
	/**
	 * @throws TemplateException
	 * @throws IOException
	 */
	public void generate() throws TemplateException, IOException{
		Map<String, Object> modelRoot = new HashMap<String, Object>();
		modelRoot.put(PACKAGE_STR, domainPackageName);
		modelRoot.put("createJavaClassName", new JavaClassNameMethod());
		
		// Generate files.
		for (ItemMetadata itemMetadata : mapping.getItemMetadata()) {
			String tableName = itemMetadata.getDomainName();
			String domainClassName = itemMetadata.getJavaClass().getSimpleName();
			String baseClassName = itemMetadata.getClassName();
			File domainJava = new File(domainDirectory, domainClassName + fileExtension);
			
			// Prepare Freemarker model for domain class generation.
			modelRoot = new HashMap<String, Object>();
            modelRoot.put("createJavaClassName", new JavaClassNameMethod());
			modelRoot.put(PACKAGE_STR, domainPackageName);
			modelRoot.put(BASE_CLASS, baseClassName);
			modelRoot.put(CLASS, domainClassName);
			modelRoot.put(TABLE, itemMetadata);
			modelRoot.put(MAPPING, mapping);
			
			String javaContent = templateManager.getTemplateContent(domainClassTemple, modelRoot);
			writeToFile(domainJava, javaContent);
			System.out.println("Created " + domainJava.getPath());
		}
	}
	
	/**
	 * @return the mapping
	 */
	public SimpleDBMapping getMapping() {
		return mapping;
	}

	/**
	 * @param mapping the mapping to set
	 */
	public void setMapping(SimpleDBMapping mapping) {
		this.mapping = mapping;
	}

	/**
	 * @return the templateManager
	 */
	public FreemarkerTemplateManager getTemplateManager() {
		return templateManager;
	}

	/**
	 * @param templateManager the templateManager to set
	 */
	public void setTemplateManager(FreemarkerTemplateManager templateManager) {
		this.templateManager = templateManager;
	}

	/**
	 * @return the domainDirectory
	 */
	public File getDomainDirectory() {
		return domainDirectory;
	}

	/**
	 * @param domainDirectory the domainDirectory to set
	 */
	public void setDomainDirectory(File domainDirectory) {
		this.domainDirectory = domainDirectory;
	}

	/**
	 * @return the domainPackageName
	 */
	public String getDomainPackageName() {
		return domainPackageName;
	}

	/**
	 * @param domainPackageName the domainPackageName to set
	 */
	public void setDomainPackageName(String domainPackageName) {
		this.domainPackageName = domainPackageName;
	}

	/**
	 * @return the domainClassTemple
	 */
	public String getDomainClassTemple() {
		return domainClassTemple;
	}

	/**
	 * @param domainClassTemple the domainClassTemple to set
	 */
	public void setDomainClassTemple(String domainClassTemple) {
		this.domainClassTemple = domainClassTemple;
	}

	/**
	 * @return the fileExtension
	 */
	public String getFileExtension() {
		return fileExtension;
	}

	/**
	 * @param fileExtension the fileExtension to set
	 */
	public void setFileExtension(String fileExtension) {
		this.fileExtension = fileExtension;
	}

	static class JavaClassNameMethod implements TemplateMethodModel {

		/* (non-Javadoc)
		 * @see freemarker.template.TemplateMethodModel#exec(java.util.List)
		 */
		public Object exec(List args) throws TemplateModelException {
			if (args.size() != 1) {
	            throw new TemplateModelException("Wrong arguments createJavaClassName(String)");
	        }

	        String arg = (String)args.get(0);
	        if(StringUtils.isNotEmpty(arg)){
	        	return new SimpleScalar(createJavaClassName(arg));
	        }
	        return new SimpleScalar(StringUtils.EMPTY);
		}
	}
	
	/**
	 * @param tableName
	 * @return
	 */
	private static String createJavaClassName(String tableName){
	    StringBuffer buff = new StringBuffer(16);
		String[] pieces = StringUtils.split(tableName, StringConstants.UNDERSCORE);
		for (int i = 0; i < pieces.length; i++) {
			String piece = pieces[i];
			if(StringUtils.isNotBlank(piece)){
				buff.append(piece.substring(0, 1).toUpperCase()).append(piece.substring(1).toLowerCase());
			}
		}
		String className = buff.toString();
		
		// Handle plural ending.
		if(className.length() > 2){
			String lastOne = className.substring(className.length()-1);
			String lastTwo = className.substring(className.length()-2);
			String lastThree = className.substring(className.length()-3);
			if(lastThree.equals(IES_ENDING)){
			    className = className.substring(0, className.length()-3) + Y_ENDING;
			}
			else if(lastTwo.equals(ES_ENDING) || (!lastTwo.equals(SS_ENDING) && lastOne.equals(S_ENDING))){
			    className = className.substring(0, className.length()-1);
			}
		}
		return className;
	}
	
	/**
	 * @param file
	 * @param content
	 */
	public static void writeToFile(File file, String content){
        writeFile(file, content, false);
    }
    
    /**
     * @param file
     * @param content
     * @param append
     */
    private static void writeFile(File file, String content, boolean append){
        BufferedWriter writer = null;
        try {
            writer = new BufferedWriter(new FileWriter(file, append));
            writer.write(content);
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
        finally{
            if(writer != null){
                try {
                    writer.close();
                } catch (IOException e) {}
            }
        }
    }
}
